import matplotlib.pyplot as plt
import numpy as np
import os
from datetime import datetime

# 设置中文字体支持
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号

# 训练数据
epochs = list(range(1, 21))
train_loss = [2.1093, 0.9483, 0.7386, 0.6066, 0.5282, 0.4651, 0.3989, 0.3736, 0.3283, 0.2988,
              0.2795, 0.2519, 0.2294, 0.2205, 0.2053, 0.1975, 0.1799, 0.1738, 0.1759, 0.1639]
train_acc = [53.22, 75.73, 80.36, 83.04, 85.38, 86.82, 88.67, 89.48, 90.87, 91.65,
             92.37, 93.03, 93.66, 93.97, 94.34, 94.76, 95.20, 95.53, 95.39, 95.94]

# 验证数据
val_loss = [0.6463, 0.6640, 0.6938, 0.6957, 0.7727, 0.7735, 0.7303, 0.7980, 0.8881, 0.8383,
            1.0859, 1.0990, 1.1089, 1.1967, 1.1670, 1.1867, 1.2611, 1.3059, 1.2960, 1.3496]
val_acc = [82.88, 83.27, 82.25, 83.67, 82.38, 82.45, 82.31, 81.55, 80.83, 82.45,
           81.16, 79.90, 81.59, 81.39, 81.12, 80.40, 80.73, 80.66, 80.66, 80.56]

def plot_metrics():
    """绘制训练和验证的准确率与损失率曲线"""
    # 创建图形和子图
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(20, 8))
    
    # 设置图表样式
    plt.style.use('ggplot')
    
    # 绘制准确率曲线
    ax1.plot(epochs, train_acc, 'o-', color='#4285F4', linewidth=2, markersize=8, label='训练准确率')
    ax1.plot(epochs, val_acc, 's-', color='#EA4335', linewidth=2, markersize=8, label='验证准确率')
    
    # 设置准确率图表属性
    ax1.set_title('训练与验证准确率', fontsize=18, fontweight='bold')
    ax1.set_xlabel('训练轮次', fontsize=14)
    ax1.set_ylabel('准确率 (%)', fontsize=14)
    ax1.set_xticks(epochs)
    ax1.set_ylim(50, 100)
    ax1.grid(True, linestyle='--', alpha=0.7)
    ax1.legend(fontsize=12)
    
    # 在准确率曲线上标注最大值
    max_train_acc_idx = np.argmax(train_acc)
    max_val_acc_idx = np.argmax(val_acc)
    
    ax1.annotate(f'最大值: {train_acc[max_train_acc_idx]}%',
                xy=(epochs[max_train_acc_idx], train_acc[max_train_acc_idx]),
                xytext=(epochs[max_train_acc_idx], train_acc[max_train_acc_idx] + 3),
                arrowprops=dict(facecolor='black', shrink=0.05, width=1.5),
                fontsize=10, ha='center')
    
    ax1.annotate(f'最大值: {val_acc[max_val_acc_idx]}%',
                xy=(epochs[max_val_acc_idx], val_acc[max_val_acc_idx]),
                xytext=(epochs[max_val_acc_idx], val_acc[max_val_acc_idx] + 3),
                arrowprops=dict(facecolor='black', shrink=0.05, width=1.5),
                fontsize=10, ha='center')
    
    # 绘制损失率曲线
    ax2.plot(epochs, train_loss, 'o-', color='#4285F4', linewidth=2, markersize=8, label='训练损失率')
    ax2.plot(epochs, val_loss, 's-', color='#EA4335', linewidth=2, markersize=8, label='验证损失率')
    
    # 设置损失率图表属性
    ax2.set_title('训练与验证损失率', fontsize=18, fontweight='bold')
    ax2.set_xlabel('训练轮次', fontsize=14)
    ax2.set_ylabel('损失率', fontsize=14)
    ax2.set_xticks(epochs)
    ax2.grid(True, linestyle='--', alpha=0.7)
    ax2.legend(fontsize=12)
    
    # 在损失率曲线上标注最小值
    min_train_loss_idx = np.argmin(train_loss)
    min_val_loss_idx = np.argmin(val_loss)
    
    ax2.annotate(f'最小值: {train_loss[min_train_loss_idx]:.4f}',
                xy=(epochs[min_train_loss_idx], train_loss[min_train_loss_idx]),
                xytext=(epochs[min_train_loss_idx], train_loss[min_train_loss_idx] - 0.2),
                arrowprops=dict(facecolor='black', shrink=0.05, width=1.5),
                fontsize=10, ha='center')
    
    ax2.annotate(f'最小值: {val_loss[min_val_loss_idx]:.4f}',
                xy=(epochs[min_val_loss_idx], val_loss[min_val_loss_idx]),
                xytext=(epochs[min_val_loss_idx], val_loss[min_val_loss_idx] - 0.2),
                arrowprops=dict(facecolor='black', shrink=0.05, width=1.5),
                fontsize=10, ha='center')
    
    # 添加网格线
    ax1.grid(True, linestyle='--', alpha=0.7)
    ax2.grid(True, linestyle='--', alpha=0.7)
    
    # 添加标题和时间戳
    plt.suptitle('ResNet101狗品种识别模型训练性能', fontsize=20, fontweight='bold')
    fig.text(0.5, 0.01, f'生成时间: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}', 
             ha='center', fontsize=10, color='gray')
    
    # 调整布局
    plt.tight_layout(rect=[0, 0.03, 1, 0.95])
    
    # 创建保存目录
    save_dir = 'plots'
    os.makedirs(save_dir, exist_ok=True)
    
    # 保存图表
    save_path = os.path.join(save_dir, f'training_metrics_{datetime.now().strftime("%Y%m%d_%H%M%S")}.png')
    plt.savefig(save_path, dpi=300, bbox_inches='tight')
    print(f'图表已保存至: {save_path}')
    
    # 显示图表
    plt.show()

def plot_combined_metrics():
    """绘制训练和验证指标的组合图表"""
    # 创建图形和子图
    fig, ax1 = plt.subplots(figsize=(12, 8))
    
    # 设置图表样式
    plt.style.use('seaborn-v0_8-darkgrid')
    
    # 准确率曲线 (左Y轴)
    line1, = ax1.plot(epochs, train_acc, 'o-', color='#4285F4', linewidth=2, markersize=6, label='训练准确率')
    line2, = ax1.plot(epochs, val_acc, 's-', color='#EA4335', linewidth=2, markersize=6, label='验证准确率')
    
    # 设置左Y轴属性
    ax1.set_xlabel('训练轮次', fontsize=14)
    ax1.set_ylabel('准确率 (%)', fontsize=14, color='#4285F4')
    ax1.tick_params(axis='y', labelcolor='#4285F4')
    ax1.set_ylim(50, 100)
    
    # 创建右Y轴
    ax2 = ax1.twinx()
    
    # 损失率曲线 (右Y轴)
    line3, = ax2.plot(epochs, train_loss, 'o--', color='#34A853', linewidth=2, markersize=6, label='训练损失率')
    line4, = ax2.plot(epochs, val_loss, 's--', color='#FBBC05', linewidth=2, markersize=6, label='验证损失率')
    
    # 设置右Y轴属性
    ax2.set_ylabel('损失率', fontsize=14, color='#34A853')
    ax2.tick_params(axis='y', labelcolor='#34A853')
    
    # 添加图例
    lines = [line1, line2, line3, line4]
    ax1.legend(lines, [l.get_label() for l in lines], loc='center right', fontsize=12)
    
    # 添加标题
    plt.title('ResNet101狗品种识别模型训练与验证指标', fontsize=18, fontweight='bold')
    
    # 添加网格线
    ax1.grid(True, linestyle='--', alpha=0.3)
    
    # 添加过拟合开始的标记
    overfitting_epoch = 8  # 大约在第8轮开始出现过拟合迹象
    plt.axvline(x=overfitting_epoch, color='red', linestyle='--', alpha=0.5)
    plt.text(overfitting_epoch + 0.2, 95, '过拟合开始', color='red', fontsize=12)
    
    # 添加时间戳
    fig.text(0.5, 0.01, f'生成时间: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}', 
             ha='center', fontsize=10, color='gray')
    
    # 调整布局
    plt.tight_layout(rect=[0, 0.03, 1, 0.97])
    
    # 创建保存目录
    save_dir = 'plots'
    os.makedirs(save_dir, exist_ok=True)
    
    # 保存图表
    save_path = os.path.join(save_dir, f'combined_metrics_{datetime.now().strftime("%Y%m%d_%H%M%S")}.png')
    plt.savefig(save_path, dpi=300, bbox_inches='tight')
    print(f'组合图表已保存至: {save_path}')
    
    # 显示图表
    plt.show()

def analyze_training_data():
    """分析训练数据并打印相关统计信息"""
    # 计算训练和验证指标的统计数据
    train_acc_mean = np.mean(train_acc)
    train_acc_std = np.std(train_acc)
    val_acc_mean = np.mean(val_acc)
    val_acc_std = np.std(val_acc)
    
    train_loss_mean = np.mean(train_loss)
    train_loss_std = np.std(train_loss)
    val_loss_mean = np.mean(val_loss)
    val_loss_std = np.std(val_loss)
    
    # 计算训练和验证指标的差异
    acc_diff = [t - v for t, v in zip(train_acc, val_acc)]
    loss_diff = [v - t for t, v in zip(train_loss, val_loss)]
    
    # 打印分析结果
    print("\n" + "="*50)
    print("训练数据分析")
    print("="*50)
    
    print("\n训练准确率:")
    print(f"  平均值: {train_acc_mean:.2f}%")
    print(f"  标准差: {train_acc_std:.2f}%")
    print(f"  最大值: {max(train_acc):.2f}% (第{train_acc.index(max(train_acc))+1}轮)")
    print(f"  最小值: {min(train_acc):.2f}% (第{train_acc.index(min(train_acc))+1}轮)")
    
    print("\n验证准确率:")
    print(f"  平均值: {val_acc_mean:.2f}%")
    print(f"  标准差: {val_acc_std:.2f}%")
    print(f"  最大值: {max(val_acc):.2f}% (第{val_acc.index(max(val_acc))+1}轮)")
    print(f"  最小值: {min(val_acc):.2f}% (第{val_acc.index(min(val_acc))+1}轮)")
    
    print("\n训练损失率:")
    print(f"  平均值: {train_loss_mean:.4f}")
    print(f"  标准差: {train_loss_std:.4f}")
    print(f"  最大值: {max(train_loss):.4f} (第{train_loss.index(max(train_loss))+1}轮)")
    print(f"  最小值: {min(train_loss):.4f} (第{train_loss.index(min(train_loss))+1}轮)")
    
    print("\n验证损失率:")
    print(f"  平均值: {val_loss_mean:.4f}")
    print(f"  标准差: {val_loss_std:.4f}")
    print(f"  最大值: {max(val_loss):.4f} (第{val_loss.index(max(val_loss))+1}轮)")
    print(f"  最小值: {min(val_loss):.4f} (第{val_loss.index(min(val_loss))+1}轮)")
    
    print("\n过拟合分析:")
    max_acc_diff = max(acc_diff)
    max_acc_diff_epoch = acc_diff.index(max_acc_diff) + 1
    print(f"  训练与验证准确率最大差异: {max_acc_diff:.2f}% (第{max_acc_diff_epoch}轮)")
    
    max_loss_diff = max(loss_diff)
    max_loss_diff_epoch = loss_diff.index(max_loss_diff) + 1
    print(f"  训练与验证损失率最大差异: {max_loss_diff:.4f} (第{max_loss_diff_epoch}轮)")
    
    # 判断过拟合开始的轮次
    # 简单判断：连续3轮验证损失增加而训练损失减少
    overfitting_epochs = []
    for i in range(2, len(epochs)-1):
        if (val_loss[i] > val_loss[i-1] and train_loss[i] < train_loss[i-1] and
            val_loss[i+1] > val_loss[i] and train_loss[i+1] < train_loss[i]):
            overfitting_epochs.append(i+1)
    
    if overfitting_epochs:
        print(f"  可能的过拟合开始轮次: {overfitting_epochs}")
    else:
        print("  未检测到明显的过拟合迹象")
    
    print("="*50)

def main():
    """主函数"""
    # 绘制分离的准确率和损失率图表
    plot_metrics()
    
    # 绘制组合图表
    plot_combined_metrics()
    
    # 分析训练数据
    analyze_training_data()

if __name__ == "__main__":
    main()